/* ------------------------------------------
 * Copyright (c) 2017, Synopsys, Inc. All rights reserved.

 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:

 * 1) Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.

 * 2) Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.

 * 3) Neither the name of the Synopsys, Inc., nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * \version 2017.03
 * \date 2014-06-25
 * \author Huaqi Fang(Huaqi.Fang@synopsys.com)
--------------------------------------------- */

/**
 * \defgroup    DEVICE_DW_SPI   Designware SPI Driver
 * \ingroup DEVICE_DW
 * \brief   Designware SPI Driver Implementation
 */

/**
 * \file
 * \brief   DesignWare SPI driver implementation based on device hal layer definition (\ref dev_spi.h)
 * \ingroup DEVICE_DW_SPI
 */
#include <string.h>

#include "inc/embARC_toolchain.h"
#include "inc/embARC_error.h"

#include "inc/arc/arc_exception.h"

#include "device/designware/spi/dw_spi_hal.h"
#include "device/designware/spi/dw_spi.h"

/**
 * \defgroup    DEVICE_DW_SPI_DEFINES   DesignWare SPI Driver Macros
 * \ingroup DEVICE_DW_SPI
 * \brief   DesignWare SPI driver macros used in spi driver
 * @{
 */
/** check expressions used in DesignWare SPI driver implementation */
#define DW_SPI_CHECK_EXP(EXPR, ERROR_CODE)      CHECK_EXP(EXPR, ercd, ERROR_CODE, error_exit)

/** convert DesignWare frequence to divisor */
#define DW_SPI_FREQ2DV(perifreq, spifreq)       ((perifreq) / (spifreq))

#ifndef DISABLE_DEVICE_OBJECT_VALID_CHECK
/** valid check of spi info object */
#define VALID_CHK_SPI_INFO_OBJECT(spiinfo_obj_ptr)      {           \
            DW_SPI_CHECK_EXP((spiinfo_obj_ptr)!=NULL, E_OBJ);       \
            DW_SPI_CHECK_EXP(((spiinfo_obj_ptr)->spi_ctrl)!=NULL, E_OBJ);   \
        }
#endif

/**
 * \defgroup    DEVICE_DW_SPI_DEF_CBR   DesignWare SPI Interrupt Callback Routine Select Marcos
 * \ingroup DEVICE_DW_SPI_DEFINES
 * \brief   DesignWare SPI interrupt callback routines select macros definitions
 * @{
 */
#define DW_SPI_RDY_SND                  (1U)    /*!< ready to send callback */
#define DW_SPI_RDY_RCV                  (2U)    /*!< ready to receive callback */
#define DW_SPI_RDY_XFER                 (3U)    /*!< ready to transfer callback */
/** @} */

/** @} */

/**
 * \defgroup    DEVICE_DW_SPI_STATIC    DesignWare SPI Driver Static Functions
 * \ingroup DEVICE_DW_SPI
 * \brief   Static or inline functions, variables for DesignWare SPI handle spi operations,
 *  only used in this file.
 * @{
 */

/** Disable designware spi device */
Inline void dw_spi_disable(DW_SPI_REG *spi_reg_ptr)
{
    /** disable spi operations, then program spi control regs is possible */
    spi_reg_ptr->SSIENR = DW_SPI_SSI_DISABLE;
}
/** Enable designware spi device */
Inline void dw_spi_enable(DW_SPI_REG *spi_reg_ptr)
{
    spi_reg_ptr->SSIENR = DW_SPI_SSI_ENABLE;
}

/** Clear all designware spi interrupt */
Inline void dw_spi_clear_interrupt_all(DW_SPI_REG *spi_reg_ptr)
{
    (void)spi_reg_ptr->ICR;
}

/** test whether spi is busy, busy return 1, else 0 */
Inline int32_t dw_spi_busy(DW_SPI_REG *spi_reg_ptr)
{
    return ((spi_reg_ptr->SR & DW_SPI_SR_BUSY) != 0);
}
/** test whether spi is ready to send, 1 ready, 0 not ready */
Inline int32_t dw_spi_putready(DW_SPI_REG *spi_reg_ptr)
{
    return ((spi_reg_ptr->SR & DW_SPI_SR_TFNF) != 0);
}
/** test whether spi is read to receive, 1 ready, 0 not ready */
Inline int32_t dw_spi_getready(DW_SPI_REG *spi_reg_ptr)
{
    return ((spi_reg_ptr->SR & DW_SPI_SR_RFNE) != 0);
}
/** write data to spi send fifo */
Inline void dw_spi_putdata(DW_SPI_REG *spi_reg_ptr, int32_t data)
{
    spi_reg_ptr->DATAREG = (uint32_t)data;
}
/** read data from spi receive fifo, return data received */
Inline int32_t dw_spi_getdata(DW_SPI_REG *spi_reg_ptr)
{
    return (int32_t)spi_reg_ptr->DATAREG;
}
/**
 * \brief   send data by spi when available,
 *  mostly used in interrupt method, non-blocked function
 * \param[in]   spi_reg_ptr spi register structure pointer
 * \param[in]   data        data to be sent
 * \retval  E_OK        send successfully
 * \retval  E_OBJ       not ready to send data
 */
Inline int32_t dw_spi_snd_dat(DW_SPI_REG *spi_reg_ptr, int32_t data)
{
    if (dw_spi_putready(spi_reg_ptr))
    {
        dw_spi_putdata(spi_reg_ptr, data);
        return E_OK;
    }

    return E_OBJ;
}
/**
 * \brief   receive one char from spi,
 *  mostly used in interrupt routine, non-blocked function
 * \param[in]   spi_reg_ptr spi register structure pointer
 * \return  data received by the spi
 */
Inline int32_t dw_spi_rcv_dat(DW_SPI_REG *spi_reg_ptr)
{
    return dw_spi_getdata(spi_reg_ptr);
}
/**
 * \brief   send char by spi in poll method, blocked function
 * \param[in]   spi_reg_ptr spi register structure pointer
 * \param[in]   data        data to be sent
 */
Inline void dw_spi_psnd_dat(DW_SPI_REG *spi_reg_ptr, int32_t data)
{
    /** wait until spi is ready to send */
    while (!dw_spi_putready(spi_reg_ptr)); /* blocked */

    /** send char */
    dw_spi_putdata(spi_reg_ptr, data);
}
/**
 * \brief   receive one char from spi in poll method, blocked function
 * \param[in]   spi_reg_ptr spi register structure pointer
 * \return  data received by the spi
 */
Inline int32_t dw_spi_prcv_dat(DW_SPI_REG *spi_reg_ptr)
{
    /** wait until spi is ready to receive */
    while (!dw_spi_getready(spi_reg_ptr)); /* blocked */

    /** receive data */
    return dw_spi_getdata(spi_reg_ptr);
}

/** Reset designware FIFO by disable spi device, then enable device */
Inline void dw_spi_reset_fifo(DW_SPI_REG *spi_reg_ptr)
{
    dw_spi_disable(spi_reg_ptr);
    dw_spi_enable(spi_reg_ptr);
}

/** Enable designware spi bit interrupt with mask */
Inline void dw_spi_unmask_interrupt(DW_SPI_REG *spi_reg_ptr, uint32_t mask)
{
    spi_reg_ptr->IMR |= mask;
}

/** Disable designware spi bit interrupt with mask */
Inline void dw_spi_mask_interrupt(DW_SPI_REG *spi_reg_ptr, uint32_t mask)
{
    spi_reg_ptr->IMR &= ~mask;
}

/** Set designware spi device frequency */
Inline void dw_spi_set_freq(DW_SPI_CTRL *spi_ctrl_ptr, uint32_t freq)
{
    uint32_t sck_divisor;
    DW_SPI_REG *spi_reg_ptr = spi_ctrl_ptr->dw_spi_regs;

    dw_spi_disable(spi_reg_ptr);

    sck_divisor = DW_SPI_FREQ2DV(spi_ctrl_ptr->dw_apb_bus_freq, freq);
    spi_reg_ptr->BAUDR = sck_divisor;
    dw_spi_enable(spi_reg_ptr);
}

/** Set designware spi device data frame size */
static int32_t dw_spi_set_dfs(DW_SPI_REG *spi_reg_ptr, uint32_t dfs)
{
    uint32_t ctrl0_reg;

    if ((dfs <= 3) || (dfs > 16)) { return -1; }

    dw_spi_disable(spi_reg_ptr);
    ctrl0_reg = spi_reg_ptr->CTRLR0;
    ctrl0_reg &= ~(DW_SPI_CTRLR0_DFS_MASK);
    spi_reg_ptr->CTRLR0 = ctrl0_reg | (dfs - 1);
    dw_spi_enable(spi_reg_ptr);

    return 0;
}

/** Choose proper designware spi clock mode setting value */
Inline uint32_t dw_spi_select_clockmode(uint32_t clk_mode)
{
    return (clk_mode << DW_SPI_CTRLR0_SC_OFS);
}

/** Set designware spi clock mode */
Inline int32_t dw_spi_set_clockmode(DW_SPI_REG *spi_reg_ptr, uint32_t clk_mode)
{
    if (clk_mode > SPI_CPOL_1_CPHA_1)
    {
        return -1;
    }

    dw_spi_disable(spi_reg_ptr);
    spi_reg_ptr->CTRLR0 &= ~(DW_SPI_CTRLR0_SC_MASK);
    spi_reg_ptr->CTRLR0 |= dw_spi_select_clockmode(clk_mode);
    dw_spi_enable(spi_reg_ptr);
    return 0;
}

/** Select a spi slave with slv_line */
Inline int32_t dw_spi_select_slave(DW_SPI_REG *spi_reg_ptr, uint32_t slv_line)
{
    /* check if spi busy */
    if (dw_spi_busy(spi_reg_ptr)) { return -1; }

    spi_reg_ptr->SER = 1 << slv_line;
    return 0;
}

/** Deselect a spi device */
Inline int32_t dw_spi_deselect_slave(DW_SPI_REG *spi_reg_ptr, uint32_t slv_line)
{
    /* check if spi busy */
    if (dw_spi_busy(spi_reg_ptr)) { return -1; }

    spi_reg_ptr->SER = 0;
    return 0;
}

Inline void dw_spi_flush_tx(DW_SPI_REG *spi_reg_ptr)
{
    dw_spi_reset_fifo(spi_reg_ptr);
}

Inline void dw_spi_flush_rx(DW_SPI_REG *spi_reg_ptr)
{
    dw_spi_reset_fifo(spi_reg_ptr);
}

/** Get TX FIFO Length.
 *    calculate spi fifo length using fifo threshold method
 *  If you attempt to set bits [7:0] of this register to
 *  a value greater than or equal to the depth of the FIFO,
 *  this field is not written and retains its current value.
 */
static uint32_t dw_spi_get_txfifo_len(DW_SPI_REG *spi_reg_ptr)
{
    uint32_t fifo_thr_lev_tmp, left, right, i;

    fifo_thr_lev_tmp = spi_reg_ptr->TXFTLR;

    if (fifo_thr_lev_tmp != 0)
    {
        left = fifo_thr_lev_tmp;
    }
    else
    {
        left = DW_SPI_MIN_FIFO_LENGTH;
    }

    right = DW_SPI_MAX_FIFO_LENGTH + 1;

    for (i = left; i <= right; i++)
    {
        spi_reg_ptr->TXFTLR = i;

        if (spi_reg_ptr->TXFTLR != i)
        {
            break;
        }
    }

    spi_reg_ptr->TXFTLR = fifo_thr_lev_tmp; /* restore old fifo threshold */

    return (i);
}

/** Get RX FIFO Length */
static uint32_t dw_spi_get_rxfifo_len(DW_SPI_REG *spi_reg_ptr)
{
    uint32_t fifo_thr_lev_tmp, left, right, i;

    fifo_thr_lev_tmp = spi_reg_ptr->RXFTLR;

    if (fifo_thr_lev_tmp != 0)
    {
        left = fifo_thr_lev_tmp;
    }
    else
    {
        left = DW_SPI_MIN_FIFO_LENGTH;
    }

    right = DW_SPI_MAX_FIFO_LENGTH + 1;

    for (i = left; i <= right; i++)
    {
        spi_reg_ptr->RXFTLR = i;

        if (spi_reg_ptr->RXFTLR != i)
        {
            break;
        }
    }

    spi_reg_ptr->RXFTLR = fifo_thr_lev_tmp; /* restore old fifo threshold */

    return (i);
}

/** Init Designware SPI Hardware */
static void dw_spi_hw_init(DW_SPI_CTRL *spi_ctrl_ptr, uint32_t clk_mode, uint32_t dfs)
{
    uint32_t ctrl0_reg = 0;
    DW_SPI_REG *spi_reg_ptr = spi_ctrl_ptr->dw_spi_regs;

    dw_spi_disable(spi_reg_ptr);

    /* Clear interrupts */
    ctrl0_reg = spi_reg_ptr->ICR;
    /* Mask all interrupts */
    spi_reg_ptr->IMR = 0;

    ctrl0_reg = DW_SPI_CTRLR0_FRF_MOTOROLA | DW_SPI_TMOD_TRANSMIT_RECEIVE \
                | dw_spi_select_clockmode(clk_mode) | (dfs - 1) | DW_SPI_CTRLR0_SLV_OE_ENABLE;
    spi_reg_ptr->CTRLR0 = ctrl0_reg;
    spi_reg_ptr->CTRLR1 = 0;

    /* deselect slaves */
    spi_reg_ptr->SER = 0;

    /* Set threshold values for both tx and rx */
    spi_reg_ptr->TXFTLR = 0;
    spi_reg_ptr->RXFTLR = 0;

    dw_spi_enable(spi_reg_ptr);
}

/** enable designware spi */
static void dw_spi_enable_device(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

    if ((spi_info_ptr->status & DEV_ENABLED) == 0)
    {
        dw_spi_enable(spi_reg_ptr);
        spi_info_ptr->status |= DEV_ENABLED;
    }
}

/** disable designware spi */
static void dw_spi_disable_device(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

    dw_spi_disable(spi_reg_ptr);
    spi_info_ptr->status &= ~DEV_ENABLED;
}


/**
 * \brief   disable designware spi send or receive interrupt
 * \param[in]   DEV_SPI_INFO    *spi_info_ptr
 * \param[in]   cbrtn       control code of callback routine of send or receive
 */
static int32_t dw_spi_dis_cbr(DEV_SPI_INFO *spi_info_ptr, uint32_t cbrtn)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    int32_t ercd = E_OK;

    if ((spi_info_ptr->status & DW_SPI_IN_XFER) != 0)   /* only in transfer need do check */
    {
        switch (cbrtn)
        {
        case DW_SPI_RDY_SND:
            DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_TX, E_CTX);
            spi_info_ptr->status &= ~(DW_SPI_IN_TX);
            break;

        case DW_SPI_RDY_RCV:
            DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_RX, E_CTX);
            spi_info_ptr->status &= ~(DW_SPI_IN_RX);
            break;

        case DW_SPI_RDY_XFER:
            DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_XFER, E_CTX);
            spi_info_ptr->status &= ~(DW_SPI_IN_XFER);
            break;

        default:
            break;
        }
    }

    dw_spi_mask_interrupt(spi_ctrl_ptr->dw_spi_regs, DW_SPI_IMR_XFER);

    if (spi_ctrl_ptr->int_status & DW_SPI_GINT_ENABLE)
    {
        int_disable(spi_ctrl_ptr->intno);
        spi_ctrl_ptr->int_status &= ~DW_SPI_GINT_ENABLE;
    }

error_exit:
    return ercd;
}

/**
 * \brief   enable DesignWare SPI send or receive interrupt
 * \param[in]   DEV_SPI_INFO    *spi_info_ptr
 * \param[in]   cbrtn       control code of callback routine of send or receive
 */
static int32_t dw_spi_ena_cbr(DEV_SPI_INFO *spi_info_ptr, uint32_t cbrtn)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    int32_t ercd = E_OK;

    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == 0, E_CTX);

    switch (cbrtn)
    {
    case DW_SPI_RDY_SND:
        spi_info_ptr->status |= DW_SPI_IN_TX;
        break;

    case DW_SPI_RDY_RCV:
        spi_info_ptr->status |= DW_SPI_IN_RX;
        break;

    case DW_SPI_RDY_XFER:
        spi_info_ptr->status |= DW_SPI_IN_XFER;
        break;

    default:
        break;
    }

    dw_spi_unmask_interrupt(spi_ctrl_ptr->dw_spi_regs, DW_SPI_IMR_XFER);

    if ((spi_ctrl_ptr->int_status & DW_SPI_GINT_ENABLE) == 0)
    {
        spi_ctrl_ptr->int_status |= DW_SPI_GINT_ENABLE;
        int_enable(spi_ctrl_ptr->intno);
    }

error_exit:
    return ercd;
}

/**
 * \brief   enable designware spi interrupt
 * \param   spi_info_ptr    spi information structure pointer
 */
static void dw_spi_enable_interrupt(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);

    int_handler_install(spi_ctrl_ptr->intno, spi_ctrl_ptr->dw_spi_int_handler);
    spi_ctrl_ptr->int_status |= DW_SPI_GINT_ENABLE;
    int_enable(spi_ctrl_ptr->intno);    /** enable spi interrupt */
}
/**
 * \brief   disable designware spi interrupt
 * \param   spi_info_ptr    spi information structure pointer
 */
static void dw_spi_disable_interrupt(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);

    /** disable spi send&receive interrupt after disable spi interrupt */
    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_SND);
    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_RCV);
    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_XFER);
    /* disable spi interrupt */
    dw_spi_mask_interrupt(spi_ctrl_ptr->dw_spi_regs, DW_SPI_IMR_XFER);
    spi_info_ptr->status &= ~DW_SPI_IN_XFER;
    int_disable(spi_ctrl_ptr->intno);
    spi_ctrl_ptr->int_status &= ~(DW_SPI_GINT_ENABLE);
}

static void dw_spi_reset_device(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

    dw_spi_disable_device(spi_info_ptr);
    dw_spi_disable_interrupt(spi_info_ptr);
    dw_spi_clear_interrupt_all(spi_reg_ptr);
    dw_spi_enable_device(spi_info_ptr);
}

/** abort current interrupt transmit transfer */
static int32_t dw_spi_abort_tx(DEV_SPI *spi_obj)
{
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);
    int32_t ercd = E_OK;

    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) != 0, E_OK);
    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_TX, E_CTX);

    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_SND);
    spi_info_ptr->status |= DEV_IN_TX_ABRT;

    if (spi_info_ptr->spi_cbs.tx_cb != NULL)
    {
        spi_info_ptr->spi_cbs.tx_cb(spi_obj);
    }

    spi_info_ptr->status &= ~(DEV_IN_TX_ABRT);

error_exit:
    return ercd;
}

/** abort current interrupt receive transfer */
static int32_t dw_spi_abort_rx(DEV_SPI *spi_obj)
{
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);
    int32_t ercd = E_OK;

    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) != 0, E_OK);
    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_RX, E_CTX);

    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_RCV);
    spi_info_ptr->status |= DEV_IN_RX_ABRT;

    if (spi_info_ptr->spi_cbs.rx_cb != NULL)
    {
        spi_info_ptr->spi_cbs.rx_cb(spi_obj);
    }

    spi_info_ptr->status &= ~(DEV_IN_RX_ABRT);

error_exit:
    return ercd;
}

/** abort current interrupt transfer */
static int32_t dw_spi_abort_xfer(DEV_SPI *spi_obj)
{
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);
    int32_t ercd = E_OK;

    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) != 0, E_OK);
    DW_SPI_CHECK_EXP((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_XFER, E_CTX);

    dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_XFER);
    spi_info_ptr->status |= DEV_IN_XFER_ABRT;

    if (spi_info_ptr->spi_cbs.xfer_cb != NULL)
    {
        spi_info_ptr->spi_cbs.xfer_cb(spi_obj);
    }

    spi_info_ptr->status &= ~(DEV_IN_XFER_ABRT);

error_exit:
    return ercd;
}

/** Get available transmit fifo count */
static int32_t dw_spi_get_txavail(DW_SPI_CTRL *spi_ctrl_ptr)
{
    int32_t tx_avail = 0;
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

#if DW_SPI_CALC_FIFO_LEN_ENABLE

    if (spi_ctrl_ptr->tx_fifo_len <= 1)
    {
        if (dw_spi_putready(spi_reg_ptr) == 1)
        {
            tx_avail = 1;
        }
        else
        {
            tx_avail = 0;
        }
    }
    else
#endif
    {
        tx_avail = spi_ctrl_ptr->tx_fifo_len - spi_reg_ptr->TXFLR;
    }

    return tx_avail;
}

/** Get available receive fifo count */
static int32_t dw_spi_get_rxavail(DW_SPI_CTRL *spi_ctrl_ptr)
{
    int32_t rx_avail = 0;
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

#if DW_SPI_CALC_FIFO_LEN_ENABLE

    if (spi_ctrl_ptr->rx_fifo_len <= 1)
    {
        if (dw_spi_getready(spi_reg_ptr) == 1)
        {
            rx_avail = 1;
        }
        else
        {
            rx_avail = 0;
        }
    }
    else
#endif
    {
        rx_avail = spi_reg_ptr->RXFLR;
    }

    return rx_avail;
}

static uint32_t dw_spi_tx_max(DW_SPI_CTRL *spi_ctrl_ptr)
{
    uint32_t tx_left, tx_room;
    DW_SPI_TRANSFER *xfer = &(spi_ctrl_ptr->dw_xfer);

    tx_left = (xfer->xfer_len - xfer->tx_idx) / xfer->nbytes;
    tx_room = dw_spi_get_txavail(spi_ctrl_ptr);

    return (tx_left < tx_room) ? tx_left : tx_room;
}

static uint32_t dw_spi_rx_max(DW_SPI_CTRL *spi_ctrl_ptr)
{
    uint32_t rx_left, rx_room;
    DW_SPI_TRANSFER *xfer = &(spi_ctrl_ptr->dw_xfer);

    rx_left = (xfer->xfer_len - xfer->rx_idx) / xfer->nbytes;
    rx_room = dw_spi_get_rxavail(spi_ctrl_ptr);

    return (rx_left < rx_room) ? rx_left : rx_room;
}

Inline int32_t dw_spi_rx_end(DW_SPI_CTRL *spi_ctrl_ptr)
{
    DW_SPI_TRANSFER *xfer = &(spi_ctrl_ptr->dw_xfer);

    return (xfer->rx_idx >= xfer->xfer_len);
}

Inline int32_t dw_spi_tx_end(DW_SPI_CTRL *spi_ctrl_ptr)
{
    DW_SPI_TRANSFER *xfer = &(spi_ctrl_ptr->dw_xfer);

    return (xfer->tx_idx >= xfer->xfer_len);
}

/** 1 for end, 0 for not end */
Inline int32_t dw_spi_xfer_end(DW_SPI_CTRL *spi_ctrl_ptr)
{
    return (dw_spi_tx_end(spi_ctrl_ptr) && dw_spi_rx_end(spi_ctrl_ptr));
}

static int32_t dw_spi_writer(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);
    DW_SPI_TRANSFER *dw_xfer = &(spi_ctrl_ptr->dw_xfer);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);
    uint32_t tx_max = dw_spi_tx_max(spi_ctrl_ptr);
    int32_t tx_w;
    uint32_t tx_cnt = tx_max;

    if (dw_xfer->tx_xfer == NULL)
    {
        return 0;
    }

    while (tx_max)
    {
        if (dw_xfer->tx_xfer->tx_idx >= dw_xfer->tx_xfer->tot_len)
        {
            dw_xfer->tx_xfer = dw_xfer->tx_xfer->next;

            if (dw_xfer->tx_xfer == NULL)
            {
                break;
            }
        }

        if ((dw_xfer->tx_xfer->tx_idx >= dw_xfer->tx_xfer->tx_ofs) \
            && (dw_xfer->tx_xfer->tx_idx < dw_xfer->tx_xfer->tx_totlen))
        {
            if (dw_xfer->nbytes == 1)
            {
                tx_w = (int32_t)(*(int8_t *)(dw_xfer->tx_xfer->tx_buf));
            }
            else
            {
                tx_w = (int32_t)(*(int16_t *)(dw_xfer->tx_xfer->tx_buf));
            }

            dw_xfer->tx_xfer->tx_buf += dw_xfer->nbytes;
        }
        else
        {
            tx_w = spi_info_ptr->dummy;
        }

        dw_spi_putdata(spi_reg_ptr, tx_w);
        dw_xfer->tx_xfer->tx_idx += dw_xfer->nbytes;
        dw_xfer->tx_idx += dw_xfer->nbytes;
        tx_max --;
    }

    return ((tx_cnt - tx_max) * dw_xfer->nbytes);
}

static int32_t dw_spi_reader(DEV_SPI_INFO *spi_info_ptr)
{
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);
    DW_SPI_TRANSFER *dw_xfer = &(spi_ctrl_ptr->dw_xfer);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);
    uint32_t rx_max = dw_spi_rx_max(spi_ctrl_ptr);
    int32_t rx_w;
    uint32_t rx_cnt = rx_max;

    if (dw_xfer->rx_xfer == NULL)
    {
        return 0;
    }

    while (rx_max)
    {
        if (dw_xfer->rx_xfer->rx_idx >= dw_xfer->rx_xfer->tot_len)
        {
            dw_xfer->rx_xfer = dw_xfer->rx_xfer->next;

            if (dw_xfer->rx_xfer == NULL)
            {
                break;
            }
        }

        rx_w = dw_spi_getdata(spi_reg_ptr);

        if ((dw_xfer->rx_xfer->rx_idx >= dw_xfer->rx_xfer->rx_ofs) \
            && (dw_xfer->rx_xfer->rx_idx < dw_xfer->rx_xfer->rx_totlen))
        {
            if (dw_xfer->nbytes == 1)
            {
                *(int8_t *)(dw_xfer->rx_xfer->rx_buf) = rx_w;
            }
            else
            {
                *(int16_t *)(dw_xfer->rx_xfer->rx_buf) = rx_w;
            }

            dw_xfer->rx_xfer->rx_buf += dw_xfer->nbytes;
        }

        dw_xfer->rx_xfer->rx_idx += dw_xfer->nbytes;
        dw_xfer->rx_idx += dw_xfer->nbytes;
        rx_max --;
    }

    return ((rx_cnt - rx_max) * dw_xfer->nbytes);
}

Inline uint32_t dw_spi_nbytes(uint32_t dfs)
{
    uint32_t nbytes = 1;

    if (dfs > 8) { nbytes = 2; }

    return nbytes;
}

static void dw_spi_init_transfer(DW_SPI_CTRL *spi_ctrl_ptr, DEV_SPI_TRANSFER *xfer, uint32_t dfs)
{
    DW_SPI_TRANSFER *dw_xfer = &(spi_ctrl_ptr->dw_xfer);
    uint32_t tot_len = 0;

    dw_xfer->tx_xfer = xfer;
    dw_xfer->rx_xfer = xfer;
    dw_xfer->tx_idx = 0;
    dw_xfer->rx_idx = 0;
    dw_xfer->nbytes = dw_spi_nbytes(dfs);

    /** Calculate all transfer length */
    while (xfer)
    {
        DEV_SPI_XFER_INIT(xfer);
        tot_len += xfer->tot_len;
        xfer = xfer->next;
    }

    dw_xfer->xfer_len = tot_len;
}

/* Check buffer align status, 0 for aligned, -1 for not-aligned */
static int32_t dw_spi_chk_xfer_aligned(DEV_SPI_TRANSFER *xfer, uint32_t dfs)
{
    uint32_t align_bytes = 1;

    if (xfer == NULL) { return -1; }

    if (dfs > 8)
    {
        align_bytes = 2;
    }
    else
    {
        return 0;
    }

    while (xfer)
    {
        /* check tx buffer align status */
        if (xfer->tx_len != 0)
        {
            if (xfer->tx_len % align_bytes) { return -1; }

            if (xfer->tx_ofs % align_bytes) { return -1; }

            if (!CHECK_ALIGN_BYTES(xfer->tx_buf, align_bytes)) { return -1; }
        }

        /* check tx buffer align status */
        if (xfer->rx_len != 0)
        {
            if (xfer->rx_len % align_bytes) { return -1; }

            if (xfer->rx_ofs % align_bytes) { return -1; }

            if (!CHECK_ALIGN_BYTES(xfer->rx_buf, align_bytes)) { return -1; }
        }

        xfer = xfer->next;
    }

    return 0;
}

static uint32_t dw_spi_poll_transfer(DEV_SPI_INFO *spi_info_ptr)
{
    uint32_t len = 0;
    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);

    spi_info_ptr->status |= DEV_IN_XFER;

    while (!dw_spi_xfer_end(spi_ctrl_ptr))
    {
        len += dw_spi_writer(spi_info_ptr);
        len += dw_spi_reader(spi_info_ptr);
    }

    spi_info_ptr->status &= ~DEV_IN_XFER;

    return len >> 1;
}

/** @} */

/**
 * \brief   open a designware spi device
 * \param[in]   spi_obj spi object pointer
 * \param[in]   mode    spi working mode (master or slave)
 * \param[in]   param   parameter, for master, param is the freq, for slave, param is dfs
 * \retval  E_OK    Open successfully without any issues
 * \retval  E_OPNED If device was opened before with different parameters,
 *          then just increase the \ref dev_spi_info::opn_cnt "opn_cnt" and return \ref E_OPNED
 * \retval  E_OBJ   Device object is not valid
 * \retval  E_SYS   Device is opened for different mode before, if you want to open it with different mode, you need to fully close it first.
 * \retval  E_PAR   Parameter is not valid
 * \retval  E_NOSPT Open settings are not supported
 */
int32_t dw_spi_open(DEV_SPI *spi_obj, uint32_t mode, uint32_t param)
{
    int32_t ercd = E_OK;
    uint32_t param2check;
    uint32_t clk_mode, dfs_val;
    uint32_t support_modes;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    DW_SPI_CHECK_EXP((mode == DEV_MASTER_MODE) || (mode == DEV_SLAVE_MODE), E_PAR);

    if (mode == DEV_SLAVE_MODE)   /* clock mode should be in the enum structure */
    {
        DW_SPI_CHECK_EXP((param >= SPI_CPOL_0_CPHA_0) && (param <= SPI_CPOL_1_CPHA_1), E_PAR);
    }
    else     /* frequence should > 0 */
    {
        DW_SPI_CHECK_EXP(param > 0, E_PAR);
    }

    /* END OF ERROR CHECK */

    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);

    /* Check supported modes, master or slave */
    support_modes = spi_ctrl_ptr->support_modes;
    DW_SPI_CHECK_EXP((((support_modes)&DW_SPI_MASTER_SUPPORTED) && (mode == DEV_MASTER_MODE)) || \
                     (((support_modes)&DW_SPI_SLAVE_SUPPORTED) && (mode == DEV_SLAVE_MODE)), E_NOSPT);

    /** Check opened before use case */
    if (spi_info_ptr->opn_cnt > 0)
    {
        if (mode != spi_info_ptr->mode)
        {
            /* current working mode is different from passing mode */
            return E_SYS;
        }

        if (mode == DEV_MASTER_MODE)   /* param is freq when as master */
        {
            param2check = spi_info_ptr->freq;
        }
        else     /* param is clk_mode when as slave */
        {
            param2check = spi_info_ptr->clk_mode;
        }

        spi_info_ptr->opn_cnt ++;

        if (param != param2check)   /* open with different speed mode */
        {
            return E_OPNED;
        }
        else
        {
            return E_OK;
        }
    }

    /* auto increase open count */
    spi_info_ptr->opn_cnt ++;

    /* Do FIFO Length get before init */
#if DW_SPI_CALC_FIFO_LEN_ENABLE
    spi_ctrl_ptr->tx_fifo_len = dw_spi_get_txfifo_len(spi_ctrl_ptr->dw_spi_regs);
    spi_ctrl_ptr->rx_fifo_len = dw_spi_get_rxfifo_len(spi_ctrl_ptr->dw_spi_regs);
#endif
    /* hardware init */
    spi_info_ptr->mode = mode;
    clk_mode = SPI_CLK_MODE_DEFAULT;
    dfs_val = SPI_DFS_DEFAULT;

    if (mode == DEV_SLAVE_MODE)
    {
        clk_mode = param;
    }

    spi_info_ptr->dfs = dfs_val;
    spi_info_ptr->clk_mode = clk_mode;
    dw_spi_hw_init(spi_ctrl_ptr, clk_mode, dfs_val);

    if (mode == DEV_MASTER_MODE)   /* Deselect all slaves, and set frequence */
    {
        dw_spi_deselect_slave(spi_ctrl_ptr->dw_spi_regs, 0);
        dw_spi_set_freq(spi_ctrl_ptr, param);
        spi_info_ptr->freq = param;
    }

    spi_info_ptr->status = DEV_ENABLED;
    spi_info_ptr->extra = NULL;
    spi_info_ptr->slave = SPI_SLAVE_NOT_SELECTED;
    spi_info_ptr->dummy = 0xff;

    spi_ctrl_ptr->int_status = 0;
    dw_spi_init_transfer(spi_ctrl_ptr, NULL, dfs_val);

    /** install spi interrupt into system */
    dw_spi_disable_interrupt(spi_info_ptr);
    int_handler_install(spi_ctrl_ptr->intno, spi_ctrl_ptr->dw_spi_int_handler);
    memset(&(spi_info_ptr->xfer), 0, sizeof(DEV_SPI_TRANSFER));
    memset(&(spi_info_ptr->spi_cbs), 0, sizeof(DEV_SPI_CBS));

error_exit:
    return ercd;
}

/**
 * \brief   close a DesignWare SPI device
 * \param[in]   spi_obj spi object pointer
 * \retval  E_OK    Close successfully without any issues(including scenario that device is already closed)
 * \retval  E_OPNED Device is still opened, the device \ref dev_spi_info::opn_cnt "opn_cnt" decreased by 1
 * \retval  E_OBJ   Device object is not valid
 */
int32_t dw_spi_close(DEV_SPI *spi_obj)
{
    int32_t ercd = E_OK;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    DW_SPI_CHECK_EXP(spi_info_ptr->opn_cnt > 0, E_OK);
    /* END OF ERROR CHECK */

    spi_info_ptr->opn_cnt --;

    if (spi_info_ptr->opn_cnt == 0)
    {
        DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);

        dw_spi_disable_interrupt(spi_info_ptr);
        dw_spi_abort_tx(spi_obj);
        dw_spi_abort_rx(spi_obj);
        memset(&(spi_info_ptr->xfer), 0, sizeof(DEV_SPI_TRANSFER));
        memset(&(spi_info_ptr->spi_cbs), 0, sizeof(DEV_SPI_CBS));
        memset(&(spi_ctrl_ptr->dw_xfer), 0, sizeof(DW_SPI_TRANSFER));
        dw_spi_disable_device(spi_info_ptr);
        spi_info_ptr->status = DEV_DISABLED;
        spi_info_ptr->extra = NULL;
    }
    else
    {
        ercd = E_OPNED;
    }

error_exit:
    return ercd;
}

/**
 * \brief   control spi by ctrl command
 * \param[in]       spi_obj     spi object pointer
 * \param[in]       ctrl_cmd    control command code to do specific spi work
 * \param[in,out]   param       parameters used to control spi or return something
 * \retval  E_OK    Control device successfully
 * \retval  E_CLSED Device is not opened
 * \retval  E_OBJ   Device object is not valid or not exists
 * \retval  E_PAR   Parameter is not valid for current control command
 * \retval  E_SYS   Control device failed, due to hardware issues, such as device is disabled
 * \retval  E_CTX   Control device failed, due to different reasons like in transfer state
 * \retval  E_NOSPT Control command is not supported or not valid
 */
int32_t dw_spi_control(DEV_SPI *spi_obj, uint32_t ctrl_cmd, void *param)
{
    int32_t ercd = E_OK;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    DW_SPI_CHECK_EXP(spi_info_ptr->opn_cnt > 0, E_CLSED);
    /* END OF ERROR CHECK */

    uint32_t val32; /** to receive unsigned int value */
    DEV_BUFFER *devbuf;

    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL *)(spi_info_ptr->spi_ctrl);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);
    DEV_SPI_TRANSFER *spi_xfer = &(spi_info_ptr->xfer);

    /* check whether current device is disabled */
    if ((spi_info_ptr->status & DEV_ENABLED) == 0)
    {
        /** When device is disabled,
         * only SPI_CMD_ENA_DEV, SPI_CMD_DIS_DEV, SPI_CMD_GET_STATUS, SPI_CMD_RESET
         * are available, other commands will return E_SYS
         */
        if ((ctrl_cmd != SPI_CMD_ENA_DEV) && \
            (ctrl_cmd != SPI_CMD_DIS_DEV) && \
            (ctrl_cmd != SPI_CMD_GET_STATUS) && \
            (ctrl_cmd != SPI_CMD_RESET))
        {
            return E_SYS;
        }
    }

    switch (ctrl_cmd)
    {
    /* Commmon commands for both master and slave mode */
    case SPI_CMD_GET_STATUS:
        DW_SPI_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
        *((int32_t *)param) = spi_info_ptr->status;
        break;

    case SPI_CMD_SET_CLK_MODE:
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        val32 = (uint32_t)param;
        DW_SPI_CHECK_EXP((val32 >= SPI_CPOL_0_CPHA_0) && (val32 <= SPI_CPOL_1_CPHA_1), E_PAR);

        if (dw_spi_set_clockmode(spi_reg_ptr, val32) == 0)
        {
            spi_info_ptr->clk_mode = val32;
        }
        else
        {
            ercd = E_SYS;
        }

        break;

    case SPI_CMD_ENA_DEV:
        dw_spi_enable_device(spi_info_ptr);
        break;

    case SPI_CMD_DIS_DEV:
        dw_spi_disable_device(spi_info_ptr);
        break;

    case SPI_CMD_RESET:
        dw_spi_reset_device(spi_info_ptr);
        break;

    case SPI_CMD_FLUSH_TX:
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        dw_spi_flush_tx(spi_reg_ptr);
        break;

    case SPI_CMD_FLUSH_RX:
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        dw_spi_flush_rx(spi_reg_ptr);
        break;

    case SPI_CMD_SET_DFS:
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        val32 = (uint32_t)param;
        DW_SPI_CHECK_EXP(val32 > 0, E_PAR);

        if (dw_spi_set_dfs(spi_reg_ptr, val32) == 0)
        {
            spi_info_ptr->dfs = val32;
        }
        else
        {
            ercd = E_SYS;
        }

        break;

    case SPI_CMD_SET_DUMMY_DATA:
        val32 = (uint32_t)param;
        spi_info_ptr->dummy = val32;
        break;

    case SPI_CMD_GET_RXAVAIL: /* Notice in bytes unit */
        DW_SPI_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
        *((int32_t *)param) = dw_spi_get_rxavail(spi_ctrl_ptr) * dw_spi_nbytes(spi_info_ptr->dfs);
        break;

    case SPI_CMD_GET_TXAVAIL: /* Notice in bytes unit */
        DW_SPI_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
        *((int32_t *)param) = dw_spi_get_txavail(spi_ctrl_ptr) * dw_spi_nbytes(spi_info_ptr->dfs);
        break;

    case SPI_CMD_SET_TXCB:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        spi_info_ptr->spi_cbs.tx_cb = param;
        break;

    case SPI_CMD_SET_RXCB:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        spi_info_ptr->spi_cbs.rx_cb = param;
        break;

    case SPI_CMD_SET_XFERCB:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        spi_info_ptr->spi_cbs.xfer_cb = param;
        break;

    case SPI_CMD_SET_ERRCB:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        spi_info_ptr->spi_cbs.err_cb = param;
        break;

    case SPI_CMD_ABORT_TX:
        ercd = dw_spi_abort_tx(spi_obj);
        break;

    case SPI_CMD_ABORT_RX:
        ercd = dw_spi_abort_rx(spi_obj);
        break;

    case SPI_CMD_ABORT_XFER:
        ercd = dw_spi_abort_xfer(spi_obj);
        break;

    case SPI_CMD_SET_TXINT:
        val32 = (uint32_t)param;

        if (val32 == 0)
        {
            ercd = dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_SND);
        }
        else
        {
            ercd = dw_spi_ena_cbr(spi_info_ptr, DW_SPI_RDY_SND);
        }

        break;

    case SPI_CMD_SET_RXINT:
        val32 = (uint32_t)param;

        if (val32 == 0)
        {
            ercd = dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_RCV);
        }
        else
        {
            ercd = dw_spi_ena_cbr(spi_info_ptr, DW_SPI_RDY_RCV);
        }

        break;

    case SPI_CMD_SET_TXINT_BUF:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);

        if (param != NULL)
        {
            devbuf = (DEV_BUFFER *)param;
            DEV_SPI_XFER_SET_TXBUF(spi_xfer, devbuf->buf, 0, devbuf->len);
            DEV_SPI_XFER_SET_RXBUF(spi_xfer, NULL, devbuf->len, 0);
            DEV_SPI_XFER_SET_NEXT(spi_xfer, NULL);
            DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned(spi_xfer, spi_info_ptr->dfs) == 0, E_PAR);
            dw_spi_init_transfer(spi_ctrl_ptr, spi_xfer, spi_info_ptr->dfs);
        }
        else
        {
            DEV_SPI_XFER_SET_TXBUF(spi_xfer, NULL, 0, 0);
            DEV_SPI_XFER_SET_RXBUF(spi_xfer, NULL, 0, 0);
            DEV_SPI_XFER_SET_NEXT(spi_xfer, NULL);
            dw_spi_init_transfer(spi_ctrl_ptr, NULL, spi_info_ptr->dfs);
        }

        break;

    case SPI_CMD_SET_RXINT_BUF:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);

        if (param != NULL)
        {
            devbuf = (DEV_BUFFER *)param;
            DEV_SPI_XFER_SET_TXBUF(spi_xfer, NULL, devbuf->len, 0);
            DEV_SPI_XFER_SET_RXBUF(spi_xfer, devbuf->buf, 0, devbuf->len);
            DEV_SPI_XFER_SET_NEXT(spi_xfer, NULL);
            /* Check transfer align */
            DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned(spi_xfer, spi_info_ptr->dfs) == 0, E_PAR);
            dw_spi_init_transfer(spi_ctrl_ptr, spi_xfer, spi_info_ptr->dfs);
        }
        else
        {
            DEV_SPI_XFER_SET_TXBUF(spi_xfer, NULL, 0, 0);
            DEV_SPI_XFER_SET_RXBUF(spi_xfer, NULL, 0, 0);
            DEV_SPI_XFER_SET_NEXT(spi_xfer, NULL);
            dw_spi_init_transfer(spi_ctrl_ptr, NULL, spi_info_ptr->dfs);
        }

        break;

    case SPI_CMD_TRANSFER_POLLING:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);

        if (param != NULL)
        {
            /* Check transfer align */
            DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned((DEV_SPI_TRANSFER *)param, spi_info_ptr->dfs) == 0, E_PAR);
            *spi_xfer = *((DEV_SPI_TRANSFER *)param);
            dw_spi_init_transfer(spi_ctrl_ptr, spi_xfer, spi_info_ptr->dfs);
            /* Transfer data by poll */
            dw_spi_poll_transfer(spi_info_ptr);
        }
        else
        {
            ercd = E_PAR;
        }

        break;

    case SPI_CMD_TRANSFER_INT:
        DW_SPI_CHECK_EXP(CHECK_ALIGN_4BYTES(param), E_PAR);

        if (param != NULL)
        {
            DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
            /* Check transfer align */
            DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned((DEV_SPI_TRANSFER *)param, spi_info_ptr->dfs) == 0, E_PAR);
            *spi_xfer = *((DEV_SPI_TRANSFER *)param);
            dw_spi_init_transfer(spi_ctrl_ptr, spi_xfer, spi_info_ptr->dfs);
            /* Transfer data by interrupt */
            ercd = dw_spi_ena_cbr(spi_info_ptr, DW_SPI_RDY_XFER);
        }
        else
        {
            ercd = dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_XFER);
        }

        break;

    /* Master mode only commands */
    case SPI_CMD_MST_SET_FREQ:
        DW_SPI_CHECK_EXP(spi_info_ptr->mode == DEV_MASTER_MODE, E_NOSPT);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        val32 = (uint32_t)param;
        DW_SPI_CHECK_EXP(val32 > 0, E_PAR);
        dw_spi_set_freq(spi_ctrl_ptr, val32);
        spi_info_ptr->freq = val32;
        break;

    case SPI_CMD_MST_SEL_DEV:
        DW_SPI_CHECK_EXP(spi_info_ptr->mode == DEV_MASTER_MODE, E_NOSPT);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        val32 = (uint32_t)param;

        if (dw_spi_select_slave(spi_reg_ptr, val32) == 0)
        {
            spi_info_ptr->slave = val32;
        }
        else
        {
            ercd = E_SYS;
        }

        break;

    case SPI_CMD_MST_DSEL_DEV:
        DW_SPI_CHECK_EXP(spi_info_ptr->mode == DEV_MASTER_MODE, E_NOSPT);
        DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
        val32 = (uint32_t)param;

        if (dw_spi_deselect_slave(spi_reg_ptr, val32) == 0)
        {
            spi_info_ptr->slave = SPI_SLAVE_NOT_SELECTED;
        }
        else
        {
            ercd = E_SYS;
        }

        break;

    /* Slave mode only commands */


    default:
        ercd = E_NOSPT;
        break;
    }

error_exit:
    return ercd;
}

/**
 * \brief   send data through DesignWare SPI
 * \param[in]   spi_obj spi object pointer
 * \param[in]   data    pointer to data need to send by spi
 * \param[in]   len length of data to be sent
 * \retval  >0  Byte count that was successfully sent for poll method
 * \retval  E_OBJ   Device object is not valid or not exists
 * \retval  E_PAR   Parameter is not valid
 * \retval  E_CTX   Device is still in transfer state
 * \retval  E_SYS   Can't write data to hardware due to hardware issues, such as device is disabled
 */
int32_t dw_spi_write(DEV_SPI *spi_obj, const void *data, uint32_t len)
{
    int32_t ercd = E_OK;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    DW_SPI_CHECK_EXP(spi_info_ptr->opn_cnt > 0, E_CLSED);
    DW_SPI_CHECK_EXP(spi_info_ptr->status & DEV_ENABLED, E_SYS);
    DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
    DW_SPI_CHECK_EXP(data != NULL, E_PAR);
    DW_SPI_CHECK_EXP(len > 0, E_PAR);
    /* END OF ERROR CHECK */

    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);
    DEV_SPI_TRANSFER spi_xfer;

    /* Master and Slave transmit */
    DEV_SPI_XFER_SET_TXBUF(&spi_xfer, data, 0, len);
    DEV_SPI_XFER_SET_RXBUF(&spi_xfer, NULL, len, 0);
    DEV_SPI_XFER_SET_NEXT(&spi_xfer, NULL);

    /* Check transfer align */
    DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned(&spi_xfer, spi_info_ptr->dfs) == 0, E_PAR);

    dw_spi_init_transfer(spi_ctrl_ptr, &spi_xfer, spi_info_ptr->dfs);

    ercd = dw_spi_poll_transfer(spi_info_ptr);

error_exit:
    return ercd;
}

/**
 * \brief   read data through DesignWare SPI
 * \param[in]   spi_info_ptr    spi information structure pointer
 * \param[out]  data        data that need to read (data must be char type)
 * \param[in]   len     data count need to read
 * \retval  >=0     data have been read
 * \retval  E_PAR       arguments passed was wrong
 * \retval  E_OBJ       spi has something error, nothing can be done
 * \retval  E_CLSED     spi was closed, not available for control
 * \retval  <0      other error code not defined here
 */
int32_t dw_spi_read(DEV_SPI *spi_obj, void *data, uint32_t len)
{
    int32_t ercd = E_OK;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    DW_SPI_CHECK_EXP(spi_info_ptr->opn_cnt > 0, E_CLSED);
    DW_SPI_CHECK_EXP(spi_info_ptr->status & DEV_ENABLED, E_SYS);
    DW_SPI_CHECK_EXP((spi_info_ptr->status & DEV_IN_XFER) == 0, E_CTX);
    DW_SPI_CHECK_EXP(data != NULL, E_PAR);
    DW_SPI_CHECK_EXP(len > 0, E_PAR);
    /* END OF ERROR CHECK */

    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);
    DEV_SPI_TRANSFER spi_xfer;

    /* Master and Slave transmit */
    DEV_SPI_XFER_SET_TXBUF(&spi_xfer, NULL, len, 0);
    DEV_SPI_XFER_SET_RXBUF(&spi_xfer, data, 0, len);
    DEV_SPI_XFER_SET_NEXT(&spi_xfer, NULL);

    /* Check transfer align */
    DW_SPI_CHECK_EXP(dw_spi_chk_xfer_aligned(&spi_xfer, spi_info_ptr->dfs) == 0, E_PAR);

    dw_spi_init_transfer(spi_ctrl_ptr, &spi_xfer, spi_info_ptr->dfs);

    ercd = dw_spi_poll_transfer(spi_info_ptr);

error_exit:
    return ercd;
}

/**
 * \brief   DesignWare SPI interrupt processing routine
 * \param[in]   spi_info_ptr    DEV_SPI_INFO *spi_info_ptr
 * \param[in]   ptr     extra information
 */
void dw_spi_isr(DEV_SPI *spi_obj, void *ptr)
{
    int32_t ercd = E_OK;
    DEV_SPI_INFO *spi_info_ptr = &(spi_obj->spi_info);

    /* START ERROR CHECK */
    VALID_CHK_SPI_INFO_OBJECT(spi_info_ptr);
    /* END OF ERROR CHECK */

    DW_SPI_CTRL *spi_ctrl_ptr = (DW_SPI_CTRL_PTR)(spi_info_ptr->spi_ctrl);
    DW_SPI_REG *spi_reg_ptr = (DW_SPI_REG *)(spi_ctrl_ptr->dw_spi_regs);

    uint32_t isr_status;

    isr_status = spi_reg_ptr->ISR;

    if (!isr_status) { return; }

    if (spi_ctrl_ptr->dw_xfer.xfer_len == 0)
    {
        dw_spi_disable_interrupt(spi_info_ptr);
    }
    else
    {
        if (isr_status & (DW_SPI_IMR_TXOIM | DW_SPI_IMR_RXOIM | DW_SPI_IMR_RXUIM))
        {
            dw_spi_clear_interrupt_all(spi_reg_ptr);
            dw_spi_disable_interrupt(spi_info_ptr);

            if (spi_info_ptr->spi_cbs.err_cb)
            {
                spi_info_ptr->spi_cbs.err_cb(spi_obj);
            }

            memset(&(spi_ctrl_ptr->dw_xfer), 0, sizeof(DW_SPI_TRANSFER));
        }

        dw_spi_reader(spi_info_ptr);

        if (isr_status & DW_SPI_IMR_TXEIM)
        {
            dw_spi_writer(spi_info_ptr);
        }

        if (dw_spi_xfer_end(spi_ctrl_ptr))
        {
            if ((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_TX)
            {
                dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_SND);

                if (spi_info_ptr->spi_cbs.tx_cb)
                {
                    spi_info_ptr->spi_cbs.tx_cb(spi_obj);
                }
            }
            else if ((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_RX)
            {
                dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_RCV);

                if (spi_info_ptr->spi_cbs.rx_cb)
                {
                    spi_info_ptr->spi_cbs.rx_cb(spi_obj);
                }
            }
            else if ((spi_info_ptr->status & DW_SPI_IN_XFER) == DW_SPI_IN_XFER)
            {
                dw_spi_dis_cbr(spi_info_ptr, DW_SPI_RDY_XFER);

                if (spi_info_ptr->spi_cbs.xfer_cb)
                {
                    spi_info_ptr->spi_cbs.xfer_cb(spi_obj);
                }
            }
            else
            {
                dw_spi_disable_interrupt(spi_info_ptr);
            }

            memset(&(spi_ctrl_ptr->dw_xfer), 0, sizeof(DW_SPI_TRANSFER));
        }
    }

error_exit:
    return;
}
/** @} */ /* DEVICE_DW_SPI_IMPLEMENT */

/** @} */ /* DEVICE_DW_SPI */
